<?php
/** 
 * Class that contains ldap functions.
 *
 * @package api
 * @author Richard Fullmer <php-api@byu.edu>
 * @version 1.0
 */
/**
 *
 */
require_once( "lang/Object.php" );
require_once( "lib/LdapException.php" );
require_once( "util/HashMap.php" );
require_once( "lang/String.php" );
require_once( "lang/Array.php" );
 
/**
 * Class Ldap
 *
 * This class is used to bind to an ldap directory.  
 *
 * @package api
 * @author Richard Fullmer <php-api@byu.edu>
 * 
 * @version 1.0
 */
class Ldap extends Object {

	// ================================= Private Members =======================================================

	/**#@+
	 * @var string
	 */
	private $server;
	private $pdn;		
	private $ppword;	
	/**#@-*/
	
	/**
	 * @var resource connection resource
	 */
	private $connection = null;
	
	
	// ====================================== Builtin Methods =================================================
	
	/**
	 * Constructor for the class.
	 * 
	 * Sets up the ldap connection to use for the rest of the duration of this object.  It also sets the 
	 * process bind account usernames and passwords.
	 * 
	 * @param string $server The ldap server to connect to.
	 * @param string $pdn Fully distinguished name (dn) of the account to use while interacting with the ldap server.
	 * @param string $ppword Password accompanying the dn.
	 * @throws LdapException - if the connection to the $server could not be established.
	 */
	public function __construct($server, $pdn = null, $ppword = null) {
		$this->server = $server;
		$this->pdn = $pdn;
		$this->ppword = $ppword;
		
		$this->init();
	}
	
	/**
	 * Internal destructor, used to close the connection to the LDAP server.
	 *
	 * This function is called when the object of this class goes out of scope, therefore it should close all it's connections.
	 */
	public function __destruct() {
		$this->close();
	}
	
	/**
	 * Internal magic method.  This method is called automatically by php when this object is unserialized.  It re-initiates its connection
	 * to the ldap server.
	 */
	public function __wakeup() {
		$this->init();
	}
	
	/**
	 * Internal magic method.  This method is called automatically by php when this object is serialized.  It closes all connections to
	 * any ldap servers it may be connected to.
	 */
	public function __sleep() {
		$this->close();
	}
	
	// ======================================== Public Methods ===================================================
	
	/**
	 * Bind to an ldap directory.
	 *
	 * @param string $bind_rdn the relative distinguished name with which to bind
	 * @param string $bind_password the password used in the bind process.
	 * @returns boolean true if the bind completed successfully, false otherwise.
	 */
	public function bind( $bind_rdn = null, $bind_password = null ) {
		return @ldap_bind( $this->getConnection(), $bind_rdn, $bind_password );
	}
	
	/**
	 * Function attempts to search for information on an ldap server.
	 *
	 * @param string $dn The distinguished name to begin the search at
	 * @param string $filter filter to use in the search
	 * @param string $needed_fields Array of fields to grab from the user in LDAP (optional)
	 * @return mixed Returns an array or attributes on success, false otherwise
	 */
	public function search($dn, $filter, $needed_fields = array() ) {
		
		$r = ldap_bind( $this->getConnection(), $this->pdn, $this->ppword);
		if (!$r) 
			$this->throwException();
						
		$sr = ldap_search( $this->getConnection(), $dn, $filter, $needed_fields );
		$entry = ldap_first_entry( $this->getConnection(), $sr);
		
		
		if ($entry) { 
			return ldap_get_attributes( $this->getConnection(), $entry);
		}
		else
			return false;
	}
	
	
	
	/**
	 * Used to retrive information about the last error that the LDAP class experianced.
	 *
	 * @return string a message about the last error encountered by this class
	 */
	public function getLastError() {
		return ldap_error( $this->getConnection());
	}
	
	/**
	 * Returns a specific error number concerning the last error experianced by this LDAP class.
	 *
	 * @return int a number representing the error code last experianced by this LDAP class.
	 * @see http://us3.php.net/manual/en/function.ldap-errno.php
	 */
	public function getLastErrorNum() {
		return ldap_errno( $this->getConnection());
	}
	
	// ====================================== Protected Methods ===============================================
		
	/**
	 * Builds a Map storing the key/value pairs of the results of an LDAP search.
	 *
	 * @return Map - map storing key/value pairs of an ldap search
	 */
	protected function buildLdapAttributesMap( $attribs_array ) {
		$map = new HashMap();
		foreach( $attribs_array as $key => $value ) {
			if (!is_int($key) and $key != "count") {			
				if ($value['count'] > 1)
					$value = new Aarray( $value );
				else
					$value = new String( $value[0] );
													
				$map->put( new String( $key ), $value);
			}
		}
		
		return $map;
	}	
	
	// ====================================== Private Methods ==================================================
	
	/**
	 * Gets the ldap connection
	 * @return resource
	 */
	private function getConnection() {
		return $this->connection;
	}	
	
	/**
	 * Throws an LdapException.
	 *
	 * If the custom message is not set, it will throw an error based on the last returned error by ldap_error.
	 * @param string $custom_message - a message to include in the thrown exception, or uses ldap_error if null.
	 */
	private function throwException( $custom_message = null) {	
		if (is_null( $custom_message ))
			throw new LdapException( ldap_error( $this->getConnection()) );
		else
			throw new LdapException( $custom_message );
	}
	
	/**
	 * Initiates a connection to the ldap server.
	 *
	 * @throws LdapException - if the classes server variable hasn't been set, or there was a problem connecting to the server.
	 */
	private function init() {
		if (!isset($this->server))
			$this->throwException( "No server specified by which to connect." );
	
		$this->connection = ldap_connect($this->server);
		if ($this->connection == false)  {
			$this->throwException();
		}	
	}
	
	/**
	 * Close the connection to the ldap server.
	 */
	private function close() {
		ldap_unbind( $this->getConnection() );
	}
	
}
?>